/*
 *   Copyright 2021 zzh
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 */

package com.aduib.boot.common.buffer;

import com.aduib.boot.common.util.ByteBufferUtils;
import com.aduib.boot.common.util.SystemUtils;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * @description: BufferUtils
 * @author: zzh
 * @date: 2021/9/16 10:47
 */
public class BufferUtils extends ByteBufferUtils {
  /**
   * 给定指定容量创建buffer，该buffer是对外内存
   *
   * @param capacity
   * @return
   */
  public static Buffer allocateBuffer(int capacity) {
    return new Buffer().capacity(capacity);
  }

  /**
   * 给定指定容量创建buffer
   *
   * @param capacity
   * @param isDirect
   * @return
   */
  public static Buffer allocateBuffer(int capacity, boolean isDirect) {
    return new Buffer().capacity(capacity, isDirect);
  }

  public static Buffer allocateBuffer(ByteBuffer byteBuffer) {
    return new Buffer().capacity(byteBuffer);
  }

  public static Buffer copy(Buffer src, Buffer dest) {
    return copy(
        src,
        src.readerIndex(),
        dest,
        dest.writerIndex(),
        Math.min(src.readableBytes(), dest.remaining()));
  }

  public static Buffer copy(Buffer src, int srcStart, Buffer dest, int destStart, int length) {
    src.switchReadMode();
    dest.switchWriteMode();
    copy(src.getByteBuffer(), srcStart, dest.getByteBuffer(), destStart, length);
    src.updateReaderIndex();
    dest.updateWriterIndex();
    dest.position(src.writerIndex());
    return dest;
  }

  public static void arrayCopy(Buffer src, int srcPos, byte[] dst, int dstPos, int length) {
    src.switchReadMode();
    copy(src.getByteBuffer(), srcPos, dst, dstPos, length);
    src.updateReaderIndex();
  }

  /** Wraps a byte array into a buffer. */
  public static Buffer wrap(byte[] array, int offset, int length) {
    // 写入数据
    return allocateBuffer(length);
  }

  /** Wraps a byte array into a buffer. */
  public static Buffer wrap(byte[] array) {
    return wrap(array, 0, array.length);
  }

  /**
   * Calculates the hash code of the specified buffer. This method is useful when implementing a new
   * buffer type.
   */
  public static int hashCode(Buffer buffer) {
    final int aLen = buffer.readableBytes();
    final int intCount = aLen >>> 2;
    final int byteCount = aLen & 3;

    int hashCode = -1;
    int arrayIndex = buffer.readerIndex();
    if (buffer.order() == ByteOrder.BIG_ENDIAN) {
      for (int i = intCount; i > 0; i--) {
        hashCode = 31 * hashCode + buffer.getInt(arrayIndex);
        arrayIndex += 4;
      }
    } else {
      for (int i = intCount; i > 0; i--) {
        hashCode = 31 * hashCode + Integer.reverseBytes(buffer.getInt(arrayIndex));
        arrayIndex += 4;
      }
    }

    for (int i = byteCount; i > 0; i--) {
      hashCode = 31 * hashCode + buffer.get(arrayIndex++);
    }

    if (hashCode == 0) {
      hashCode = 1;
    }

    return hashCode;
  }

  /**
   * Returns {@code true} if and only if the two specified buffers are identical to each other for
   * {@code length} bytes starting at {@code aStartIndex} index for the {@code a} buffer and {@code
   * bStartIndex} index for the {@code b} buffer. A more compact way to express this is:
   *
   * <p>{@code a[aStartIndex : aStartIndex + length] == b[bStartIndex : bStartIndex + length]}
   */
  public static boolean equals(Buffer a, int aStartIndex, Buffer b, int bStartIndex, int length) {

    if (a.writerIndex() - length < aStartIndex || b.writerIndex() - length < bStartIndex) {
      return false;
    }

    final int longCount = length >>> 3;
    final int byteCount = length & 7;

    if (a.order() == b.order()) {
      for (int i = longCount; i > 0; i--) {
        if (a.getLong(aStartIndex) != b.getLong(bStartIndex)) {
          return false;
        }
        aStartIndex += 8;
        bStartIndex += 8;
      }
    } else {
      for (int i = longCount; i > 0; i--) {
        if (a.getLong(aStartIndex) != Long.reverseBytes(b.getLong(bStartIndex))) {
          return false;
        }
        aStartIndex += 8;
        bStartIndex += 8;
      }
    }

    for (int i = byteCount; i > 0; i--) {
      if (a.get(aStartIndex) != b.get(bStartIndex)) {
        return false;
      }
      aStartIndex++;
      bStartIndex++;
    }

    return true;
  }

  /**
   * Returns {@code true} if and only if the two specified buffers are identical to each other as
   * described in {@link Buffer#equals(Object)}. This method is useful when implementing a new
   * buffer type.
   */
  public static boolean equals(Buffer bufferA, Buffer bufferB) {
    if (bufferA == bufferB) {
      return true;
    }
    final int aLen = bufferA.readableBytes();
    if (aLen != bufferB.readableBytes()) {
      return false;
    }
    return equals(bufferA, bufferA.readerIndex(), bufferB, bufferB.readerIndex(), aLen);
  }

  protected static void freeBuffer(Buffer buffer) {
    if (SystemUtils.useDirectBufferNoCleaner()) {
      SystemUtils.freeDirectNoCleaner(buffer.getByteBuffer());
    } else {
      SystemUtils.freeDirectBuffer(buffer.getByteBuffer());
    }
  }

  public static BufferedInputStream newBufferedInputStream(Buffer buffer) {
    return new BufferedInputStream(buffer);
  }

  public static BufferedOutputStream newBufferedOutputStream(Buffer buffer) {
    return new BufferedOutputStream(buffer);
  }

  public static BufferedOutputStream newBufferedOutputStream(int capacity) {
    return new BufferedOutputStream(capacity);
  }
}
